package com.jfirer.validator.context;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator;
import javax.validation.ValidationException;
import javax.validation.metadata.ConstraintDescriptor;
import com.jfirer.validator.ConstraintViolationGenerator;
import com.jfirer.validator.WritableConstraintViolation;
import com.jfirer.validator.constraint.WritableConstraintViolationImpl;
import com.jfirer.validator.path.api.ConnectionNode;
import com.jfirer.validator.validation.MessageContextImpl;

public class ConstraintValidatorContextImpl implements ConstraintValidatorContext, ConstraintViolationGenerator
{
    private       ConstraintDescriptor<?> constraintDescriptor;
    private       boolean                 disableDefault    = false;
    private final String                  defaultMessageTemplate;
    private       ConnectionNode          connectionNode;
    private       List<ViolationContext>  violationContexts = new ArrayList<ViolationContext>();
    private       MessageInterpolator     messageInterpolator;
    private       Object                  validatedValue;
    private       Object                  leafBean;
    
    public ConstraintValidatorContextImpl(ConnectionNode connectionNode, ConstraintDescriptor<?> constraintDescriptor, MessageInterpolator messageInterpolator, Object leafBean, Object validatedValue)
    {
        this.connectionNode = connectionNode;
        this.constraintDescriptor = constraintDescriptor;
        this.messageInterpolator = messageInterpolator;
        this.validatedValue = validatedValue;
        this.leafBean = leafBean;
        defaultMessageTemplate = constraintDescriptor.getMessageTemplate();
    }
    
    @Override
    public <T> Set<ConstraintViolation<T>> generate()
    {
        Set<ConstraintViolation<T>> result = new HashSet<ConstraintViolation<T>>();
        for (ViolationContext violationContext : violationContexts)
        {
            String messageTemplate = violationContext.messageTemplate;
            String                         message             = messageInterpolator.interpolate(messageTemplate, new MessageContextImpl(validatedValue, constraintDescriptor));
            WritableConstraintViolation<T> constraintViolation = new WritableConstraintViolationImpl<T>();
            constraintViolation.setConstraintDescriptor(constraintDescriptor);
            constraintViolation.setInvalidValue(validatedValue);
            constraintViolation.setLeafBean(leafBean);
            constraintViolation.setMessage(message);
            constraintViolation.setMessageTemplate(messageTemplate);
            constraintViolation.setPropertyPath(violationContext.connectionNode.toPath());
            result.add(constraintViolation);
        }
        if (disableDefault == false)
        {
            WritableConstraintViolation<T> constraintViolation = new WritableConstraintViolationImpl<T>();
            constraintViolation.setConstraintDescriptor(constraintDescriptor);
            constraintViolation.setInvalidValue(validatedValue);
            constraintViolation.setLeafBean(leafBean);
            constraintViolation.setMessage(messageInterpolator.interpolate(defaultMessageTemplate, new MessageContextImpl(validatedValue, constraintDescriptor)));
            constraintViolation.setMessageTemplate(defaultMessageTemplate);
            constraintViolation.setPropertyPath(connectionNode.toPath());
            result.add(constraintViolation);
        }
        return result;
    }
    
    @Override
    public void disableDefaultConstraintViolation()
    {
        disableDefault = true;
    }
    
    @Override
    public String getDefaultConstraintMessageTemplate()
    {
        return defaultMessageTemplate;
    }
    
    @Override
    public ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate)
    {
        return new ConstraintViolationBuilderImpl(messageTemplate, connectionNode, this);
    }
    
    protected ConstraintValidatorContext addCustomConstraintViolation(ConnectionNode connectionNode, String messageTemplate)
    {
        violationContexts.add(new ViolationContext(connectionNode, messageTemplate));
        return this;
    }
    
    @Override
    public <T> T unwrap(Class<T> type)
    {
        throw new ValidationException();
    }
    
    class ViolationContext
    {
        private ConnectionNode connectionNode;
        private String         messageTemplate;
        
        public ViolationContext(ConnectionNode connectionNode, String messageTemplate)
        {
            this.connectionNode = connectionNode;
            this.messageTemplate = messageTemplate;
        }
        
        public ConnectionNode getConnectionNode()
        {
            return connectionNode;
        }
        
        public String getMessageTemplate()
        {
            return messageTemplate;
        }
        
    }
    
}
